package models

import (
	"context"
	"crypto/ecdsa"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/gin-gonic/gin"
	"github.com/mr-tron/base58/base58"
	"go-chain-api/global"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo/options"
	"math"
	"math/big"
	"net/http"
	"strconv"
	"strings"
	"time"
)

type UserPrivateKey struct {
	Id                 string  `bson:"_id,omitempty" json:"id"`
	UserId             string  `bson:"userId" json:"userId"`
	MainCoinType       int64   `bson:"mainCoinType" json:"mainCoinType"`       // udun的id
	FeeAddress         string  `bson:"feeAddress" json:"feeAddress"`           // 手续费地址-钱包地址
	CollectAddress     string  `bson:"collectAddress" json:"collectAddress"`   // 归集地址
	WithdrawAddress    string  `bson:"withdrawAddress" json:"withdrawAddress"` // 提币地址-钱包地址
	ChainName          string  `bson:"chainName" json:"chainName"`
	MinAmount          float64 `bson:"minAmount" json:"minAmount"`                   // 最小归集金额
	FeePrivateKey      string  `bson:"feePrivateKey" json:"feePrivateKey"`           //手续费私钥加密后的字符串
	WithdrawPrivateKey string  `bson:"withdrawPrivateKey" json:"withdrawPrivateKey"` //提现私钥加密后的字符串
	FeeBusinessId      string  `bson:"feeBusinessId" json:"-"`                       //业务id
	WithdrawBusinessId string  `bson:"withdrawBusinessId" json:"-"`                  //业务id
}

type AddressInfo struct {
	Id              string `bson:"_id,omitempty" json:"id"`
	MainCoinType    int64  `bson:"mainCoinType" json:"mainCoinType"`
	FeeAddress      string `bson:"feeAddress" json:"feeAddress"`
	CollectAddress  string `bson:"collectAddress" json:"collectAddress"`
	WithdrawAddress string `bson:"withdrawAddress" json:"withdrawAddress"`
	ChainName       string `bson:"chainName" json:"chainName"`
	Balance         string `bson:"balance" json:"balance"`                 //手续费地址公链币余额
	WithdrawBalance string `bson:"withdrawBalance" json:"withdrawBalance"` //提现地址公链币余额
}

type AddressFee struct {
	FeeAddress        string     `bson:"feeAddress" json:"feeAddress"`
	FeeBalance        string     `bson:"feeBalance" json:"feeBalance"`
	WithdrawAddress   string     `bson:"withdrawAddress" json:"withdrawAddress"`
	WithdrawBalance   []TokenFee `bson:"withdrawBalance" json:"withdrawBalance"`
	CollectionAddress string     `bson:"collectionAddress" json:"collectionAddress"`
	CollectionFee     []TokenFee `bson:"collectionFee" json:"collectionFee"`
}

type TokenFee struct {
	Name    string `bson:"name" json:"name"`
	Balance string `bson:"balance" json:"balance"`
}

type AddressData struct {
	Code int `json:"code"`
	Data struct {
		Address string `json:"address"`
		Id      string `json:"id"`
	} `json:"data"`
	Msg string `json:"msg"`
}

func (u UserPrivateKey) Create() error {
	client := global.DBEngine.Database(global.DbConfig.DbName).Collection("user_private_key")
	MinAmountStr := fmt.Sprintf("%.1f", u.MinAmount)
	u.MinAmount, _ = strconv.ParseFloat(MinAmountStr, 64)

	filter := bson.M{
		"userId":          u.UserId,
		"mainCoinType":    u.MainCoinType,
		"feeAddress":      u.FeeAddress,
		"collectAddress":  u.CollectAddress,
		"withdrawAddress": u.WithdrawAddress,
		"minAmount":       u.MinAmount,
	}

	documents, err := client.CountDocuments(context.TODO(), filter)
	if err != nil {
		return err
	}
	if documents != 0 {
		return errors.New("data is exist")
	}
	_, err = client.InsertOne(context.TODO(), u)
	if err != nil {
		return err
	}
	return nil
}

func (u UserPrivateKey) GetListByPage(pageIndex uint, pageSize uint, c *gin.Context) ([]*AddressInfo, int64, error) {
	client := global.DBEngine.Database(global.DbConfig.DbName).Collection("user_private_key")
	findOptions := options.Find()
	findOptions.SetSkip(int64(pageIndex))
	findOptions.SetLimit(int64(pageSize))
	findOptions.SetSort(bson.D{{"_id", -1}})

	data, err := client.Find(context.TODO(), bson.M{"userId": u.UserId}, findOptions)
	if err != nil {
		return nil, 0, err
	}

	var addressInfoList []*AddressInfo

	//var tokenInfo TokenInfo
	for data.Next(context.Background()) {
		// 添加到userPrivateKey中
		var user AddressInfo
		err := data.Decode(&user)
		if err != nil {
			return nil, 0, err
		}

		config, err := GetInfoInfoByName(user.ChainName, user.MainCoinType)
		if err != nil {
			return nil, 0, err
		}

		//usdtInfo := tokenInfo.GetTokenInfoByAddress(config.UsdtAddress)
		//if usdtInfo == nil {
		//	return nil, 0, err
		//}

		// TRX地址特殊处理
		if user.MainCoinType == 648126 {
			user.Balance, err = GetTrxAmount(user.FeeAddress, "_")
			if err != nil {
				return nil, 0, err
			}
			user.WithdrawBalance, err = GetTrxAmount(user.WithdrawAddress, "_")
			if err != nil {
				return nil, 0, err
			}

			//user.WithdrawBalance, err = GetTrxAmount(user.WithdrawAddress, usdtInfo.Address)
			//if err != nil {
			//	return nil, 0, err
			//}
		} else {

			//增加超时时间-超过5s就报错
			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			defer cancel()
			client, err := ethclient.DialContext(ctx, config.RpcUrl)
			if err != nil {
				user.Balance = "0"
				user.WithdrawBalance = "0"
			} else {
				balance, _ := client.BalanceAt(c, common.HexToAddress(user.FeeAddress), nil)
				ethBalance := new(big.Float).Quo(new(big.Float).SetInt(balance), big.NewFloat(1e18))
				ethBalanceStr := ethBalance.Text('f', 9)
				user.Balance = ethBalanceStr

				result, _ := client.BalanceAt(c, common.HexToAddress(user.WithdrawAddress), nil)
				withdrawBalance := new(big.Float).Quo(new(big.Float).SetInt(result), big.NewFloat(1e18))
				withdrawBalanceStr := withdrawBalance.Text('f', 9)
				user.WithdrawBalance = withdrawBalanceStr

				//user.WithdrawBalance, _ = GetTokenBalance(client, c, config.UsdtAddress, user.WithdrawAddress, int32(usdtInfo.Decimals))
			}
			defer client.Close()
		}

		addressInfoList = append(addressInfoList, &user)
	}
	return addressInfoList, int64(len(addressInfoList)), nil
}

type TrxToken struct {
	TokenID      string `json:"tokenId"`
	Balance      string `json:"balance"`
	TokenAbbr    string `json:"tokenAbbr"`
	TokenDecimal uint   `json:"tokenDecimal"`
}

type TrxAccount struct {
	WithPriceTokens []TrxToken `json:"withPriceTokens"`
}

func GetTrxAmount(address string, tokenId string) (string, error) {
	rpcUrl := "https://apilist.tronscanapi.com/api/accountv2?address="

	resp, err := http.Get(rpcUrl + address)
	if err != nil {
		fmt.Println("err1", err)
		return "", err
	}
	defer resp.Body.Close()

	var account TrxAccount
	if err := json.NewDecoder(resp.Body).Decode(&account); err != nil {
		fmt.Println("err2", err)
		return "", err
	}

	result := account.WithPriceTokens

	fee := "0"
	for _, token := range result {
		if token.TokenID == tokenId {
			if token.Balance != "0" {
				num, _ := strconv.ParseFloat(token.Balance, 64)
				denominator := math.Pow(10, float64(token.TokenDecimal))
				fee = strconv.FormatFloat(num/denominator, 'f', -1, 64)
				break
			}

		}
	}

	return fee, nil

}

func GetTrxBigAmount(address string, tokenId string) (string, error) {
	rpcUrl := "https://apilist.tronscanapi.com/api/accountv2?address="

	resp, err := http.Get(rpcUrl + address)
	if err != nil {
		fmt.Println("err1", err)
		return "", err
	}
	defer resp.Body.Close()

	var account TrxAccount
	if err := json.NewDecoder(resp.Body).Decode(&account); err != nil {
		fmt.Println("err2", err)
		return "", err
	}

	result := account.WithPriceTokens

	fee := "0"
	for _, token := range result {
		if token.TokenID == tokenId {
			if token.Balance != "0" {
				fee = token.Balance
				break
			}

		}
	}

	return fee, nil

}

func (u UserPrivateKey) Update() error {
	client := global.DBEngine.Database(global.DbConfig.DbName).Collection("user_private_key")

	update := bson.M{}
	if u.MinAmount != 0 {
		MinAmountStr := fmt.Sprintf("%.1f", u.MinAmount)
		u.MinAmount, _ = strconv.ParseFloat(MinAmountStr, 64)
		update["minAmount"] = u.MinAmount
	}

	//if u.WithdrawAddress != "" {
	//	update["withdrawAddress"] = u.WithdrawAddress
	//}
	if u.CollectAddress != "" {
		update["collectAddress"] = u.CollectAddress
	}
	//if u.FeeAddress != "" {
	//	update["feeAddress"] = u.FeeAddress
	//}

	objId, err := primitive.ObjectIDFromHex(u.Id)

	_, err = client.UpdateOne(context.TODO(), bson.M{"_id": objId}, bson.M{"$set": update})
	if err != nil {
		return err
	}
	return nil
}

func (u *UserPrivateKey) GetInfo() (*UserPrivateKey, error) {
	client := global.DBEngine.Database(global.DbConfig.DbName).Collection("user_private_key")
	objId, err := primitive.ObjectIDFromHex(u.Id)
	err = client.FindOne(context.TODO(), bson.M{"_id": objId}).Decode(&u)
	if err != nil {
		return nil, err
	}
	return u, nil

}

func (u UserPrivateKey) Delete() error {
	client := global.DBEngine.Database(global.DbConfig.DbName).Collection("user_private_key")
	objId, err := primitive.ObjectIDFromHex(u.Id)
	_, err = client.DeleteOne(context.TODO(), bson.M{"_id": objId, "userId": u.UserId})
	if err != nil {
		return err
	}
	return nil
}

func PrivateToAddress(privateKey string) (string, error) {

	priKey, err := crypto.HexToECDSA(privateKey)
	if err != nil {
		return "", err
	}
	//priKeyBytes := crypto.FromECDSA(priKey)
	//fmt.Printf("私钥为: %s\n", hex.EncodeToString(priKeyBytes))

	pubKey := priKey.Public().(*ecdsa.PublicKey)
	// 获取地址
	addr := crypto.PubkeyToAddress(*pubKey)

	return addr.String(), nil
}

func TRXPrivateToAddress(privateKey string) (string, error) {
	priKey, err := crypto.HexToECDSA(privateKey)
	if err != nil {
		return "", err
	}
	publicKey := priKey.Public()
	publicKeyECDSA, _ := publicKey.(*ecdsa.PublicKey)
	addrStr := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
	addrStr = "41" + addrStr[2:]
	addb, _ := hex.DecodeString(addrStr)
	firstHash := sha256.Sum256(addb)
	secondHash := sha256.Sum256(firstHash[:])
	secret := secondHash[:4]
	addb = append(addb, secret...)
	address := base58.Encode(addb)
	return address, nil
}

func GetTokenBalance(client *ethclient.Client, c *gin.Context, tokenAddress string, accountAddress string, decimals int32) (string, error) {

	erc20Abi := `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]`

	// 代币合约ABI
	parsedABI, err := abi.JSON(strings.NewReader(erc20Abi))
	if err != nil {

		return "0", err
	}
	contractAddress := common.HexToAddress(tokenAddress)
	data, err := parsedABI.Pack("balanceOf", common.HexToAddress(accountAddress))
	if err != nil {
		return "0", err
	}
	// 调用balanceOf方法
	callResult, err := client.CallContract(c, ethereum.CallMsg{
		To:   &contractAddress,
		Data: data,
		From: common.HexToAddress(accountAddress),
	}, nil)
	if err != nil {
		return "0", err
	}

	// 解析结果
	balance, err := parsedABI.Unpack("balanceOf", callResult)
	if err != nil {

		return "0", err
	}
	tokenBalance := new(big.Float).Quo(new(big.Float).SetInt(balance[0].(*big.Int)), big.NewFloat(math.Pow10(int(decimals))))
	balanceStr := tokenBalance.Text('f', int(decimals)) // 将结果格式化为固定点数表示，小数点后保留的位数与代币的小数位数相同
	return balanceStr, nil
}
